/**
 * @author: Loy-winters1224@163.com
 * @date: 2014-9-4-下午5:07:43
 */
package net.loyintean.pearlshell.task.orientedgraph;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

import net.loyintean.pearlshell.task.orientedgraph.task.TaskConfig;
import net.loyintean.pearlshell.task.orientedgraph.task.TaskNode;

/**
 * the factory to build graph
 * 
 * @author Loy-winters1224@163.com
 * @version 2014-9-4.1.0.0
 */
public class TaskGraphFactory {

	/**
	 * configs used to build tasks.<br>
	 * these configs defined a oriented graph architecture.
	 */
	private List<TaskConfig> configs = new LinkedList<TaskConfig>();

	/**
	 * this factory provides task executors.
	 * <p>
	 * the executors are used to buid taskNodes.
	 * 
	 */
	private TaskExecutorFactory executorFactory;

	/**
	 * this constructor indecate the executorFactory.
	 * 
	 * @author Loy-winters1224@163.com
	 * @param eFactory
	 */
	public TaskGraphFactory(TaskExecutorFactory eFactory) {
		super();
		this.executorFactory = eFactory;
	}

	/**
	 * add a config into this factory
	 * 
	 * @param c
	 * @return
	 */
	public final TaskGraphFactory addConfig(TaskConfig c) {
		this.configs.add(c);
		return this;
	}

	/**
	 * build a serial of task node by the {@link #configs}.
	 * 
	 * @return a serial of TaskNode. they will be executed as an oriented graph.
	 */
	public final Collection<TaskNode> build(Object... params) {

		// as TaskNode is multi-thread unsafe, this nodeMap have to be local
		// variable.
		Map<String, TaskNode> nodeMap = new HashMap<String, TaskNode>(
				(int) (configs.size() / 0.75));

		for (TaskConfig config : configs) {

			// read the taskNode dedecate by the config.
			// if no taskNode in nodeMap already, then new an instance and put
			// it into the nodeMap.
			TaskNode node = readTaskNode(config.getName(), nodeMap);

			// init the task with some arguments.
			initTaskNode(node, config.getFrom().size(), params);

			configLatchTo(config, nodeMap);

		}

		return nodeMap.values();
	}

	/**
	 * config relation about node point to another.
	 * 
	 * @param config
	 *            the config of former node.
	 * @param nodeMap
	 *            the map of nodes.
	 */
	private void configLatchTo(TaskConfig config, Map<String, TaskNode> nodeMap) {
		assert config != null;
		assert config.getFrom() != null;
		assert nodeMap != null;

		CountDownLatch nodeLatch = nodeMap.get(config.getName()).getFrom();

		for (String fromName : config.getFrom()) {

			TaskNode fromNode = readTaskNode(fromName, nodeMap);

			fromNode.addTo(nodeLatch);
		}
	}

	/**
	 * read a node from nodeMap according to it's name.
	 * <p>
	 * or new an instance and regidit it into nodeMap.
	 * 
	 * @param name
	 *            node's name
	 * @param nodeMap
	 *            map of node and it's name.
	 * @return a node in the map indecated by it's name.
	 */
	private TaskNode readTaskNode(String name, Map<String, TaskNode> nodeMap) {
		assert name != null;
		assert nodeMap != null;

		TaskNode node = nodeMap.get(name);

		if (node == null) {
			node = new TaskNode(name);
			nodeMap.put(name, node);
		}

		return node;
	}

	/**
	 * init a task node by name and nodeMap.
	 * <p>
	 * it will try to get a node from nodeMap. if it's null, then instance a new
	 * node, and put it into the map.
	 * 
	 * @param node
	 *            the key of the node in nodeMap
	 * @param fromSize
	 *            the size of the node's from list node itself.
	 * @praram params the arguments used to init the node.
	 */
	private void initTaskNode(TaskNode node, int fromSize, Object... params) {
		assert node != null;
		assert fromSize > -1;

		node.setFrom(new CountDownLatch(fromSize));
		node.setExecutor(executorFactory.build(node.getName()));
		node.setParams(params);
	}

	/**
	 * @param executorFactory
	 *            the {@link #executorFactory }to set
	 */
	public void setExecutorFactory(TaskExecutorFactory executorFactory) {
		this.executorFactory = executorFactory;
	}

}
